package com.pinfly.common.crypto;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import com.pinfly.common.util.HexConvert;

/**
 * A stream used to read X509 and Microsoft RSA keys
 */
public class KeyInputStream extends ByteArrayInputStream
{
    private boolean m_useLittleEndian = false;

    /**
     * Creates a new stream
     * 
     * @param data The byte array to create the stream from. This should contain
     *            the X509 or MSRSA key.
     * @param useLittleEndian If true, it indicates that the data array being
     *            passed in is in Little-endian format, otherwise use
     *            Big-endian. MS RSA keys are in Little-endian format.
     */
    public KeyInputStream (byte[] data, boolean useLittleEndian)
    {
        super (data);
        m_useLittleEndian = useLittleEndian;
    }

    /**
     * Reads the next byte of data from this input stream. If no byte is
     * available because the end of the stream has been reached, an IOException
     * is thrown.
     * 
     * @return The byte read
     * @throws IOException if the end of the stream is reached
     */
    public byte readByte () throws IOException
    {
        byte[] x = read (1);
        return (x[0]);
    }

    /**
     * Reads an integer from the stream.
     * 
     * @return The int read
     * @throws IOException if the end of the stream is reached, or the data in
     *             the stream can not be converted into an Integer.
     */
    public int readInt () throws IOException
    {
        String asHex = null;

        try
        {
            // 4 bytes per int
            byte[] results = read (4);
            asHex = HexConvert.convertHexString (results);
            return Integer.decode ("0x" + asHex).intValue ();
        }
        catch (NumberFormatException nfe)
        {
            throw new IOException ("Failed to convert 0x" + asHex + " into an Integer.");
        }

    }

    /**
     * Reads a short from the stream.
     * 
     * @return The short read
     * @throws IOException if the end of the stream is reached, or the data in
     *             the stream can not be converted into a Short.
     */
    public short readShort () throws IOException
    {
        String asHex = null;

        try
        {
            // 2 bytes per short
            byte[] results = read (2);
            asHex = HexConvert.convertHexString (results);
            return Short.decode ("0x" + asHex).shortValue ();

        }
        catch (NumberFormatException nfe)
        {
            throw new IOException ("Failed to convert 0x" + asHex + " into a Short.");
        }
    }

    /**
     * Reads an array of bytes from the stream. If useLittleEndian was set to
     * true in the constructor for this class, then the byte array contents are
     * reversed prior to being returned.
     * 
     * @param c The number of bytes to read.
     * @return An array of the bytes read.
     * @throws IOException if the end of the stream is reached, or if an attempt
     *             is made to read more bytes than available in the stream. In
     *             the case of this second failure, the current pos in the
     *             stream is set to the end.
     */
    byte[] read (int c) throws IOException
    {
        byte[] tmp = new byte[c];

        int x = read (tmp, 0, c);

        // Need to see if we tried to read more bytes than exists
        if (x == -1)
        {
            throw new IOException ("End of stream reached");
        }

        if (x < c)
        {
            throw new IOException ("Attempt to read more bytes than are available");
        }

        return tmp;
    }

    /**
     * Reads up to len bytes of data into an array of bytes from this input
     * stream. If pos equals count, then -1 is returned to indicate end of file.
     * Otherwise, the number k of bytes read is equal to the smaller of len and
     * count-pos. If k is positive, then bytes buf[pos] through buf[pos+k-1] are
     * copied into b[off] through b[off+k-1] in the manner performed by
     * System.arraycopy. The value k is added into pos and k is returned. If
     * useLittleEndian was set to true in the constructor for this class, then
     * the byte array contents are reversed prior to being returned.
     * 
     * @param b the buffer into which the data is read.
     * @param off the start offset of the data.
     * @param len the maximum number of bytes read.
     * @return the total number of bytes read into the buffer, or -1 if there is
     *         no more data because the end of the stream has been reached.
     */
    public int read (byte[] b, int off, int len)
    {
        int x = super.read (b, off, len);

        if (m_useLittleEndian)
        {
            // Reverse the bytes
            KeyUtil.reverseBytes (b);
        }

        return (x);

    }

}
